package com.genoworks.gcconsole;

import java.io.InputStreamReader;

public class GcParser {

  private static final String[] SAMPLES = new String[] {
      "[GC 119037K->104763K(230124K), 0.0064765 secs]",
      "[GC [PSYoungGen: 101207K->1063K(200256K)] 1425948K->1325892K(2002816K), 0.0117190 secs]",
      "[Full GC [PSYoungGen: 296K->0K(51008K)] [PSOldGen: 728K->831K(466048K)] 1024K->831K(517056K) "
          + "[PSPermGen: 6871K->6871K(16384K)], 0.0469740 secs]",
      "324442.431: [Full GC [PSYoungGen: 35083K->0K(466944K)] [PSOldGen: 1384225K->1330342K(1398784K)] "
          + "1419308K->1330342K(1865728K) [PSPermGen: 178817K->178817K(524288K)], 3.6474370 secs] "
          + "[Times: user=3.63 sys=0.01, real=3.65 secs] ",
      "143.464: [Full GC (System) [PSYoungGen: 4430K->0K(51008K)] [PSOldGen: 0K->4390K(466048K)] "
          + "4430K->4390K(517056K) [PSPermGen: 9384K->9384K(18944K)], 5.9641560 secs] "
          + "[Times: user=0.08 sys=0.65, real=5.96 secs]" };

  private static final String PLAIN_GC = "GC";
  private static final String FULL_GC = "Full GC";
  private static final String FULL_GC_SYSTEM = "Full GC (System)";
  private static final String SECS = "secs";
  private static final String YOUNG_GEN = "PSYoungGen";
  private static final String OLD_GEN = "PSOldGen";
  private static final String PERM_GEN = "PSPermGen";

  private static final String ARROW = "->";

  static GcEvent parseLine( String line ) {
    //    System.out.println( line );
    try {

      //
      GcEvent result = new GcEvent();
      result.setSource( line );

      /**
       * 타임스탬프가 있는 경우, 맨 앞의 타임스탬프를 제거.
       */
      int idx = line.indexOf( '[' );
      if ( idx > 0 ) {
        line = line.substring( idx ).trim();
      }

      /**
       * 맨 뒤에 Times 섹션이 있다면 모두 제거하고
       * trim.
       */
      idx = line.indexOf( "[Times:" );
      if ( idx >= 0 ) {
        line = line.substring( 0, idx ).trim();
      }

      /**
       * 앞뒤에 있는 [] 괄호를 제거.
       */
      line = line.substring( 1, line.length() - 1 );

      /**
       * 일반 "GC"인지, "Full GC"인지 확인.
       */
      result.setFull( !line.startsWith( PLAIN_GC )
          && line.startsWith( FULL_GC )
          && !line.startsWith( FULL_GC_SYSTEM ) );
      result.setFullSystem( line.startsWith( FULL_GC_SYSTEM ) );
      if ( result.isFullSystem() ) {
        line = line.substring( FULL_GC_SYSTEM.length() ).trim();
      } else if ( result.isFull() ) {
        line = line.substring( FULL_GC.length() ).trim();
      } else {
        line = line.substring( PLAIN_GC.length() ).trim();
      }

      /**
       * 쉼표를 기준으로 메모리 현황과 걸린 시간을 분리.
       */
      final String[] gcAndTime = line.split( "," );
      if ( gcAndTime.length != 2 ) {
        // TODO
        return null;
      }

      /**
       * 시간 값을 읽어서 보관.
       */
      result.setDuration( Double.parseDouble( gcAndTime[ 1 ].substring(
          0,
            gcAndTime[ 1 ].length() - SECS.length() ).trim() ) );
      //    System.out.println( "duration "
      //        + new DecimalFormat( "###,##0.00" ).format( result.getDuration() ) );

      /**
       * GC 상태 값을 읽어들인다.
       */
      final String[] gcStatus = gcAndTime[ 0 ].split( "\\[|\\]" );
      for ( String str : gcStatus ) {
        final String strTrimmed = str.trim();
        if ( strTrimmed.length() == 0 ) {
          continue;
        }
        final String type = getType( strTrimmed );
        if ( type == null ) {
          result.setHeapPre( getPreAmount( strTrimmed ) );
          result.setHeapPost( getPostAmount( strTrimmed ) );
          result.setHeapLimit( getLimitAmount( strTrimmed ) );
        } else if ( YOUNG_GEN.equals( type ) ) {
          result.setYoungGenPre( getPreAmount( strTrimmed ) );
          result.setYoungGenPost( getPostAmount( strTrimmed ) );
          result.setYoungGenLimit( getLimitAmount( strTrimmed ) );
        } else if ( OLD_GEN.equals( type ) ) {
          result.setOldGenPre( getPreAmount( strTrimmed ) );
          result.setOldGenPost( getPostAmount( strTrimmed ) );
          result.setOldGenLimit( getLimitAmount( strTrimmed ) );
        } else if ( PERM_GEN.equals( type ) ) {
          result.setPermGenPre( getPreAmount( strTrimmed ) );
          result.setPermGenPost( getPostAmount( strTrimmed ) );
          result.setPermGenLimit( getLimitAmount( strTrimmed ) );
        } else {
          // TODO
        }
        //      System.out.println( "type " + type + ", pre " + pre + ", post " + post + ", limit " + limit );
      }

      return result;

    } catch( Throwable t ) {
      //      System.out.println( line );
      t.printStackTrace();
      System.exit( 0 );
    }
    return null;
  }

  private static String getType( String gcStatus ) {
    int idx = gcStatus.indexOf( ':' );
    if ( idx >= 0 ) {
      return gcStatus.substring( 0, idx );
    }
    return null;
  }

  private static int getPreAmount( String gcStatus ) {
    // 101207K->1063K(200256K)
    int idx = gcStatus.indexOf( ':' );
    if ( idx > 0 ) {
      gcStatus = gcStatus.substring( idx + 1 ).trim();
    }
    return getBytes( gcStatus.substring( 0, gcStatus.indexOf( ARROW ) ) );
  }

  private static int getPostAmount( String gcStatus ) {
    // 101207K->1063K(200256K)
    return getBytes( gcStatus.substring(
        gcStatus.indexOf( ARROW ) + ARROW.length(),
          gcStatus.indexOf( '(' ) ) );
  }

  private static int getLimitAmount( String gcStatus ) {
    // 101207K->1063K(200256K)
    return getBytes( gcStatus.substring( gcStatus.indexOf( '(' ) + 1, gcStatus.indexOf( ')' ) ) );
  }

  private static int getBytes( String str ) {
    if ( str.endsWith( "K" ) ) {
      return Integer.parseInt( str.substring( 0, str.length() - 1 ) ) * 1024;
    } else if ( str.endsWith( "M" ) ) {
      return Integer.parseInt( str.substring( 0, str.length() - 1 ) ) * 1024 * 1024;
    } else if ( str.endsWith( "G" ) ) {
      return Integer.parseInt( str.substring( 0, str.length() - 1 ) ) * 1024 * 1024 * 1024;
    }
    return Integer.parseInt( str );
  }

  public static void main( String[] args ) {
    for ( String sample : SAMPLES ) {
      parseLine( sample );
    }
    GcReader rd = new GcReader( new InputStreamReader( System.in ) );
    rd.addCollectionListener( new ConsoleWriter( System.out ) );
    new Thread( rd ).start();
  }
}
